package com.nulldev.util.net.dsap;

import java.net.DatagramPacket;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.lang.json.JsonObject;
import com.nulldev.util.net.experimental.udp2.Client;
import com.nulldev.util.net.packets.NetInfo;
import com.nulldev.util.net.packets.NetInfoAdapter;
import com.nulldev.util.net.packets.NetPacket;
import com.nulldev.util.net.packets.PacketManager;
import com.nulldev.util.net.packets.PacketMode;
import com.nulldev.util.net.tcp.client.TCPClient;
import com.nulldev.util.net.udp.UDPClient;
import com.nulldev.util.threading.legacy.ThreadExecutor;

public class DSAPAllocation {
	
	private final String ip;
	private final int port;
	private final boolean encryption;
	private final DSAPRequestServerType type;
	
	public DSAPAllocation(final JsonObject obj) {
		if (obj.getString("error") != null) {
			throw new UnsupportedOperationException(obj.getString("error"));
		}
		final String ip = obj.getString("ip");
		final int port = obj.getInt("port", -1);
		if (port == -1) {
			throw new UnsupportedOperationException("DSAP: Corrupted data recieved!");
		}
		final String mode0 = obj.getString("mode");
		final boolean dsap_encryption = obj.getBoolean("enc");
		this.ip = ip;
		this.port = port;
		this.encryption = dsap_encryption;
		this.type = DSAPRequestServerType.get(mode0);
	}
	
	public String getIP() {
		return this.ip;
	}
	
	public int getPort() {
		return this.port;
	}
	
	public boolean encryptionSupported() {
		return this.encryption;
	}
	
	public DSAPRequestServerType getType() {
		return this.type;
	}
	
	@Override
	public String toString() {
		return "DSAP.Allocation[ip=" + this.ip + ",port=" + this.port + ",encryption=" + this.encryption + ",type=" + this.type + "]";
	}

	private boolean created, closed;
	
	private Object obj = null;
	
	public void stop() {
		if (this.obj == null) {
			return;
		}
		if (this.obj instanceof TCPClient) {
			final TCPClient client = (TCPClient) this.obj;
			client.sendBlocking(PacketManager.fromID("dsap.socket.close", ""));
			client.requestDisconnect();
			client.waitForClose(2000);
			client.stop();
			this.obj = null;
			this.closed = true;
			this.created = false;
		}else if (this.obj instanceof UDPClient) {
			final UDPClient client = (UDPClient) this.obj;
			try {
				client.reply(PacketManager.fromID("dsap.socket.close", "").toString());
				final FinalBypass<Boolean> expr = new FinalBypass<Boolean>(false);
				ThreadExecutor.execute(new Runnable() {

					@Override
					public void run() {
						while (!expr.getObject()) {
							try {
								//JVMUtil.async_println("Asking to stop...");
								client.reply(PacketManager.fromID("dsap.socket.close", "").toString());
								Thread.sleep((1000 / 64));
							}catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
					
				});
				Thread.sleep(2500L);
				expr.setObject(true);
				client.close();
				this.obj = null;
				this.closed = true;
				this.created = false;
			}catch (Exception e) {
				e.printStackTrace();
				this.obj = null;
				this.closed = true;
				this.created = false;
			}
		}else if (this.obj instanceof Client) {
			final Client cl = (Client) this.obj;
			ThreadExecutor.execute(new Runnable() {
				
				@Override
				public void run() {
					cl.sendReliableData(PacketManager.fromID("dsap.socket.close", "").toString().getBytes());
				}
			});
			try {
				Thread.sleep(2500L);
			} catch (InterruptedException e) {
			}
			cl.disconnectInactiveServer();
			cl.killConnection();
			this.closed = true;
			this.created = false;
			this.obj = null;
		}
	}
	
	public boolean sendBlocking(final NetPacket packet) {
		return this.sendBlocking(packet, PacketMode.defaultMode, null);
	}
	
	public boolean sendBlocking(final NetPacket packet, final PacketMode mode) {
		return this.sendBlocking(packet, mode, null);
	}
	
	public boolean sendBlocking(final NetPacket packet, final PacketMode mode, final NetInfo netInfo) {
		if (this.obj == null || !this.created || this.closed) {
			return false;
		}
		if (this.obj instanceof TCPClient) {
			return ((TCPClient)this.obj).sendBlocking(packet, mode, netInfo);
		}else if (this.obj instanceof UDPClient) {
			try {
				((UDPClient)this.obj).reply(packet.toString(mode, netInfo));
				return true;
			}catch (Exception e) {
				return false;
			}
		}else if (this.obj instanceof Client) {
			((Client)this.obj).sendReliableData(packet.toString(mode, netInfo).getBytes());
			return true;
		}
		return false;
	}
	
	public NetInfo attemptNetInfo() {
		if (this.obj instanceof TCPClient) {
			return NetInfoAdapter.create(((TCPClient) this.obj).getConnection());
		}
		return NetInfoAdapter.generic();
	}
	
	public void create() {
		if (this.created) {
			return;
		}
		if (this.closed) {
			return;
		}
		if (this.type == DSAPRequestServerType.TCP) {
			final TCPClient tcpc = new TCPClient("DSAP: Allocated Client", "dsap.allocClient", this.ip, this.port) {
				
				@Override
				public void receive(String event, String payload, byte[] bytes, long count, final long readTime) {
					final NetPacket packet = PacketManager.parsePacket(this, null, event, bytes);
					if (!packet.isGeneric()) {
						packet.client(packet.getData(), event, NetInfoAdapter.create(this.getConnection()));
					}
				}
			};
			tcpc.start();
			if (!tcpc.waitForConnection(2500)) {
				tcpc.requestDisconnect();
				tcpc.waitForClose(1500);
				tcpc.stop();
				this.created = false;
				this.obj = null;
				return;
			}
			if (this.encryption) {
				tcpc.createKeyPair();
			}
			this.obj = tcpc;
			this.created = true;
		}else if (this.type == DSAPRequestServerType.UDP) {
			try {
				final UDPClient udpc = new UDPClient(this.ip, this.port, 64, 64000) {
					
					@Override
					public void handle_EXT(String data, DatagramPacket p) throws Exception {
						final NetPacket packet = PacketManager.parsePacket(this, null, data);
						if (!packet.isGeneric()) {
							packet.client(packet.getData(), data, NetInfoAdapter.create(this));
						}
					}
				};
				udpc.start();
				this.obj = udpc;
				this.created = true;
			} catch (Exception e) {
				e.printStackTrace();
				this.created = false;
				return;
			}
		}else if (this.type == DSAPRequestServerType.RUDP) {
			final Client c = new Client(this.port, this.ip);
			c.startClient();
			c.sendPing();
			this.created = true;
			this.obj = c;
		}
	}
}
